﻿using System;
using System.Linq;
using System.Web.Mvc;
using Dndz.Industries.Models;
using Dndz.Industries.Settings;
using Orchard.ContentManagement.MetaData;
using Orchard.Mvc;

namespace Dndz.Industries.Services
{
    public class DefaultCaptchaService : ICaptchaService
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IContentDefinitionManager _contentDefinitionManager;

        public DefaultCaptchaService(IHttpContextAccessor httpContextAccessor,
            IContentDefinitionManager contentDefinitionManager)
        {
            _httpContextAccessor = httpContextAccessor;
            _contentDefinitionManager = contentDefinitionManager;
        }

        public string GenerateCaptcha(string contentType, string challengeGuid, CaptchaTypePartSettings settings)
        {
            var curHttpContext = _httpContextAccessor.Current();

            // Generate and store random solution text
            var session = curHttpContext.Session;
            session[CaptchaServiceConstants.SESSION_KEY_PREFIX + challengeGuid] = MakeRandomSolution(settings);

            var urlHelper = new UrlHelper(curHttpContext.Request.RequestContext);

            return urlHelper.Action("Render", "CaptchaImage",
                new { area = "Dndz.Industries", challengeGuid, contentType });
        }

        private static string MakeRandomSolution(CaptchaTypePartSettings settings)
        {
            Random rng = new Random();

            char[] buf = new char[settings.TotalChars];
            for (int i = 0; i < settings.TotalChars; i++)
            {
                Func<char> RndDigit = () => (char)('0' + rng.Next(10));
                Func<char> RndLetter = () => (char)('a' + rng.Next(26));

                if (settings.IncDigits && settings.IncLetters)
                    buf[i] = rng.Next(11) > 5 ? RndDigit() : RndLetter();
                else if (settings.IncDigits)
                    buf[i] = RndDigit();
                else
                    buf[i] = RndLetter();
            }

            return new string(buf);
        }

        public CaptchaTypePartSettings LoadSettings(string contentType)
        {
            var settings = _contentDefinitionManager
                .GetTypeDefinition(contentType)
                .Parts
                .Where(p => p.PartDefinition.Name == typeof(CaptchaPart).Name)
                .First()
                .Settings
                .GetModel<CaptchaTypePartSettings>();

            return settings;
        }

        public bool IsCaptchaValid(string contentTypeFieldName, string guidFieldName, string textBoxFieldName)
        {
            var curHttpContext = _httpContextAccessor.Current();
            var contentTypeFieldValue = curHttpContext.Request.Params[contentTypeFieldName];
            
            if (string.IsNullOrEmpty(contentTypeFieldValue)) return false;
            
            var settings = LoadSettings(contentTypeFieldValue);

            if (settings.IsForNotAuthUsersOnly && curHttpContext.Request.IsAuthenticated)
                return true;

            var challengeGuid = curHttpContext.Request.Params[guidFieldName];
            var attemptedSolution = curHttpContext.Request.Params[textBoxFieldName];
            var guidKey = CaptchaServiceConstants.SESSION_KEY_PREFIX + challengeGuid;

            // Immediately remove the solution from Session to prevent replay attacks
            var solution = (string)curHttpContext.Session[guidKey];
            curHttpContext.Session.Remove(guidKey);

            return guidKey != null && solution != null &&
                attemptedSolution.Equals(solution, StringComparison.OrdinalIgnoreCase);
        }
    }
}
